package org.diy.traffic.daemon;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;

import org.diy.traffic.sources.TrafficSource;
import org.diy.traffic.sources.TrafficSource.TrafficSourceException;
import org.diy.traffic.updates.TrafficUpdate;
import org.diy.traffic.util.TLog;


/*
 * This class represents a daemon which checks list of registered TrafficSources
 * (obtained from an AirTrafficController instance) for updates, and then adds these
 * updates to a queue (also obtained from an ATC instance)
 */
public class InputDaemon extends Thread {
		
		/* A value in milliseconds which represents how long this thread should sleep before in between checks on the sources */
		private long sleepInterval; 
		
		/* boolean value which represents if this thread should be alive. NOT REALLY USED! */
		private volatile boolean IS_ALIVE = true;
				
		/* A reference to an ATC which provides info about sources and a queue in which to place updates */
		private AirTrafficController atController;

		/*
		 * Constructor for InputDaemon
		 * @param sleepFor A value in milliseconds which represents how long the daemon should sleep in between checks. If this
		 * 		is not provided, it defaults to 30000ms == 30 seconds
		 * @param atc A reference to an AirTrafficController instance
		 */
		public InputDaemon(long sleepFor, AirTrafficController atc) {
			sleepInterval = sleepFor;
			atController = atc;
		}
		
		public void doRevive()  {
			IS_ALIVE = true;
		}
		
		public void doSuspend() {
			IS_ALIVE = false;
		}
		
		/* Helper function to sleep for a specified period of time */
		public void sleepForInterval(long interval) {
			try { Thread.sleep(interval);} 
			catch (InterruptedException ie) {
				//System.out.println("Error sleeping " + "\n" + ie.getMessage());
			}
		}
		
		/* Helper function which checks the TrafficSource's last time of update, and compares this to 
		 * the intended update frequency for that TrafficSource.
		 * @param t The TrafficSource to check if it's time to update
		 * @return true if it's time to udpate this source, false if not
		 */
		private boolean isTimeForUpdate(TrafficSource t) {
			if ((System.currentTimeMillis() - t.getLastCheck()) >= t.getUpdateFrequency()) {
				return true;
			} else return false;
		}
		
		/*
		 * Attempts to update a TrafficSource, checking first to see if it's time to update that source based on it's individual settings
		 * @param ts TrafficSource to attempt to update
		 * @return A list of TrafficUpdate objects, or null if it's not time to update. If it is time to update, and there just are no updates, the list is empty.
		 */
		private List<TrafficUpdate> tryUpdateOnSource(TrafficSource ts) throws TrafficSourceException {
			TLog.log(Level.FINEST, String.format("~~~Attempting update on source %s", ts.getName()));
			if (!isTimeForUpdate(ts)) {
				return null;
			}
			TLog.log(Level.FINE, "Time to update " + ts.getName() + "; performing check now");		
			List<TrafficUpdate> updates = ts.doCheck(true); // add a reference to the ATC here?
			return updates;
		}
		

		/*
		 * (non-Javadoc)
		 * This represents the 'meat' of the InputDaemon
		 * @see java.lang.Runnable#run()
		 */
		public void run() {
			while (IS_ALIVE) {
				
				/*if (!IS_ALIVE) {
					try {
						TLog.log(Level.CONFIG, "InputThread going to bed");
						TLog.log(Level.CONFIG, Thread.currentThread().getName());
						wait();
					} catch (InterruptedException e) {
						TLog.log(Level.CONFIG, "InputThread done waiting!");
					}
				}*/
				
				//Get a list of sources from the ATC
				ArrayList<TrafficSource> trafficSources = atController.getTrafficSources();
				TLog.log(Level.FINER, "<<<<Input Daemon checking traffic sources (" + trafficSources.size()+ " registered)>>>>");
				
				// Check each source
				Iterator sourceI = trafficSources.iterator();
				while (sourceI.hasNext()) {
					TrafficSource sourceB = (TrafficSource) sourceI.next();
					
					List<TrafficUpdate> updates;
					try {
						// Attempt an update on the source, depending on whether or not that source is due for a check
						updates = tryUpdateOnSource(sourceB);
					} catch (TrafficSourceException te) {
						TLog.log(Level.WARNING, 
								String.format("Error while checking source %s: %s", sourceB, te.getMessage())
								);
						continue;
					} catch (Exception e) {
						TLog.log(Level.SEVERE, 
								String.format("UNKNOWN ERROR while checking source %s: %s", sourceB, e.getMessage())
								);
						TLog.log(Level.SEVERE,e);
						continue;
					}
					
					// if updates is null, this means that it wasn't time yet to update that source
					if (updates != null) { //if it was time for an update
						if (updates.size() > 0) {
							TLog.log(Level.FINE, String.format("[%s] adding %d updates to the queue", sourceB, updates.size() ));
							logUpdates(updates);
							
							// add the updates to the ATC's udpate queue
							// should i make this a seperate function which each source can call if they want?
							// pass a reference to this (InputDaemon) to each source...
							try {
								atController.addUpdates(updates);
							} catch (IllegalStateException iae) {
								TLog.log(Level.WARNING, iae);
								TLog.log(Level.WARNING, "InputDaemon overflowed the update queue!");
							}
						}

					} else {
						TLog.log(Level.FINEST, "Not time for update for source " + sourceB.getName());
					}
				}
				
				// Run garbage collection here...
				System.gc();
				
				// sleep for a pre-defined interval
				sleepForInterval(sleepInterval);
			}
		}		
		
		/*
		 * Utility function to print out a List of updates to the log.
		 */
		private void logUpdates(List<TrafficUpdate> updates) {
			TLog.log(Level.FINEST, "Traffic updates from sources:");
			Iterator<TrafficUpdate> i = updates.iterator();
			while (i.hasNext()) {
				TLog.log(Level.FINEST, (i.next()).toString());
			}
		}
}
